package com.training.bt.tracker;

import com.turn.ttorrent.bcodec.BEValue;
import com.turn.ttorrent.common.Peer;
import com.turn.ttorrent.common.Torrent;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CustomTrackedPeer extends Peer {
    private String passkey;

    private static final Logger logger = LoggerFactory.getLogger(CustomTrackedPeer.class);

    private static final int FRESH_TIME_SECONDS = 30;

    private long uploaded;
    private long downloaded;
    private long left;
    private Torrent torrent;

    /**
     * Represents the state of a peer exchanging on this torrent.
     *
     * <p>
     * Peers can be in the STARTED state, meaning they have announced
     * themselves to us and are eventually exchanging data with other peers.
     * Note that a peer starting with a completed file will also be in the
     * started state and will never notify as being in the completed state.
     * This information can be inferred from the fact that the peer reports 0
     * bytes left to download.
     * </p>
     *
     * <p>
     * Peers enter the COMPLETED state when they announce they have entirely
     * downloaded the file. As stated above, we may also elect them for this
     * state if they report 0 bytes left to download.
     * </p>
     *
     * <p>
     * Peers enter the STOPPED state very briefly before being removed. We
     * still pass them to the STOPPED state in case someone else kept a
     * reference on them.
     * </p>
     */
    public enum PeerState {
        UNKNOWN,
        STARTED,
        COMPLETED,
        STOPPED;
    };

    private PeerState state;
    private Date lastAnnounce;

    /**
     * Instantiate a new tracked peer for the given torrent.
     *
     * @param torrent The torrent this peer exchanges on.
     * @param ip      The peer's IP address.
     * @param port    The peer's port.
     * @param peerId  The byte-encoded peer ID.
     */
    public CustomTrackedPeer(Torrent torrent, String ip, int port,
            ByteBuffer peerId, String passkey) {
        super(ip, port, peerId);
        this.torrent = torrent;

        this.passkey = passkey;

        // Instantiated peers start in the UNKNOWN state.
        this.state = PeerState.UNKNOWN;
        this.lastAnnounce = null;

        this.uploaded = 0;
        this.downloaded = 0;
        this.left = 0;
    }

    public CustomTrackedPeer(Torrent torrent, String ip, int port,
            ByteBuffer peerId) {
        this(torrent, ip, port, peerId, null);
    }

    /**
     * Update this peer's state and information.
     *
     * <p>
     * <b>Note:</b> if the peer reports 0 bytes left to download, its state will
     * be automatically be set to COMPLETED.
     * </p>
     *
     * @param state      The peer's state.
     * @param uploaded   Uploaded byte count, as reported by the peer.
     * @param downloaded Downloaded byte count, as reported by the peer.
     * @param left       Left-to-download byte count, as reported by the peer.
     */
    public void update(PeerState state, long uploaded, long downloaded,
            long left) {

        if (PeerState.STARTED.equals(state) && left == 0) {
            state = PeerState.COMPLETED;
        }

        if (!state.equals(this.state)) {
            logger.info("Peer {} {} download of {}.",
                    new Object[] {
                            this,
                            state.name().toLowerCase(),
                            this.torrent,
                    });
        }

        this.state = state;
        this.lastAnnounce = new Date();
        this.uploaded = uploaded;
        this.downloaded = downloaded;
        this.left = left;
    }

    /**
     * Tells whether this peer has completed its download and can thus be
     * considered a seeder.
     */
    public boolean isCompleted() {
        return PeerState.COMPLETED.equals(this.state);
    }

    /**
     * Returns how many bytes the peer reported it has uploaded so far.
     */
    public long getUploaded() {
        return this.uploaded;
    }

    /**
     * Returns how many bytes the peer reported it has downloaded so far.
     */
    public long getDownloaded() {
        return this.downloaded;
    }

    /**
     * Returns how many bytes the peer reported it needs to retrieve before
     * its download is complete.
     */
    public long getLeft() {
        return this.left;
    }

    /**
     * Tells whether this peer has checked in with the tracker recently.
     *
     * <p>
     * Non-fresh peers are automatically terminated and collected by the
     * Tracker.
     * </p>
     */
    public boolean isFresh() {
        return (this.lastAnnounce != null &&
                (this.lastAnnounce.getTime() + (FRESH_TIME_SECONDS * 1000) > new Date().getTime()));
    }

    /**
     * Returns a BEValue representing this peer for inclusion in an
     * announce reply from the tracker.
     *
     * The returned BEValue is a dictionary containing the peer ID (in its
     * original byte-encoded form), the peer's IP and the peer's port.
     */
    public BEValue toBEValue() throws UnsupportedEncodingException {
        Map<String, BEValue> peer = new HashMap<String, BEValue>();
        if (this.hasPeerId()) {
            peer.put("peer id", new BEValue(this.getPeerId().array()));
        }
        peer.put("ip", new BEValue(this.getIp(), Torrent.BYTE_ENCODING));
        peer.put("port", new BEValue(this.getPort()));
        return new BEValue(peer);
    }

    public String getPasskey() {
        return this.passkey;
    }

    public void setPasskey(String passkey) {
        this.passkey = passkey;
    }

    public Torrent getTorrent() {
        return this.torrent;
    }

    public void setTorrent(Torrent torrent) {
        this.torrent = torrent;
    }

    public boolean isActiveSeeder() {
        return isCompleted() && isFresh();
    }

    
}